package com.sjgs.gis.config;

import com.mongodb.Block;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import com.mongodb.connection.ClusterSettings;
import com.mongodb.connection.ConnectionPoolSettings;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.domain.EntityScanner;
import org.springframework.boot.autoconfigure.mongo.MongoClientFactory;
import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.gridfs.GridFsOperations;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableConfigurationProperties({FileSystemProperties.class})
public class FileSystemConfiguration {

    @Bean(value = "mongofsMongoProperties")
    @ConditionalOnProperty(value = "filesystem.gridfs.enabled", havingValue = "true")
    MongoProperties getMongoProperties(FileSystemProperties fileSystemProperties) {
        FileSystemProperties.Gridfs gridfs = fileSystemProperties.getGridfs();
        MongoProperties mongoProperties = new MongoProperties();
        mongoProperties.setHost(gridfs.getHost());
        mongoProperties.setPort(gridfs.getPort());
        mongoProperties.setDatabase(gridfs.getDatabase());
        mongoProperties.setAuthenticationDatabase(gridfs.getAuthenticationDatabase());
        mongoProperties.setUsername(gridfs.getUsername());
        mongoProperties.setPassword(gridfs.getPassword().toCharArray());
        return mongoProperties;
    }

    @Bean(value = "mongofsMongoClientSettings")
    @ConditionalOnBean(name = "mongofsMongoProperties")
    MongoClientSettings getMongoClientSettings(FileSystemProperties fileSystemProperties) {
        FileSystemProperties.Gridfs gridfs = fileSystemProperties.getGridfs();
        //副本集
        final List<ServerAddress> servers = new ArrayList<ServerAddress>();
        servers.add(new ServerAddress(gridfs.getHost(), gridfs.getPort()));
        //认证信息
        char[] password = gridfs.getPassword().toCharArray(); // the password as a character array
        MongoCredential credential = MongoCredential.createCredential(gridfs.getUsername(),
                gridfs.getAuthenticationDatabase(),
                password);
        return MongoClientSettings.builder()
                .credential(credential)
                .applyToClusterSettings(new Block<ClusterSettings.Builder>() {
                    public void apply(ClusterSettings.Builder builder) {
                        //TODO set ClusterSettings
                        builder.hosts(servers);
                        builder.serverSelectionTimeout(10L, TimeUnit.SECONDS);
                    }
                })
                .applyToConnectionPoolSettings(new Block<ConnectionPoolSettings.Builder>() {
                    @Override
                    public void apply(ConnectionPoolSettings.Builder builder) {
                        //TODO set ConnectionPoolSettings
                        builder.maxSize(gridfs.getMaxSize()).minSize(gridfs.getMinSize()).build();
                    }
                })
                .build();
    }

    @Bean("mongofsMongoClient")
    @ConditionalOnBean(name = "mongofsMongoProperties")
    public MongoClient mongo(Environment environment,MongoProperties mongofsMongoProperties,MongoClientSettings mongofsMongoClientSettings) {
        List<MongoClientSettingsBuilderCustomizer> builderCustomizers = new ArrayList<>();
        return (MongoClient) (new MongoClientFactory(mongofsMongoProperties, environment, builderCustomizers)
                .createMongoClient(mongofsMongoClientSettings));
    }

    @Bean("mongofsGridFSBucket")
    @ConditionalOnBean(name = "mongofsMongoClient")
    public GridFSBucket getBucket(MongoClient mongofsMongoClient,FileSystemProperties fileSystemProperties) {
        FileSystemProperties.Gridfs gridfs = fileSystemProperties.getGridfs();
        MongoDatabase database = mongofsMongoClient.getDatabase(gridfs.getGridFsDatabase());
        return GridFSBuckets.create(database, gridfs.getBucket());
    }

    @Bean("mongofsGridFsOperations")
    @ConditionalOnBean(name = "mongofsMongoClient")
    public GridFsOperations getGridFsOperations(SimpleMongoClientDbFactory mongofsSimpleMongoClientDbFactory, MappingMongoConverter mongofsMappingMongoConverter,FileSystemProperties fileSystemProperties) {
        FileSystemProperties.Gridfs gridfs = fileSystemProperties.getGridfs();
        return new GridFsTemplate(mongofsSimpleMongoClientDbFactory, mongofsMappingMongoConverter, gridfs.getBucket());
    }

    @Bean("mongofsMongoTemplate")
    @ConditionalOnBean(name = "mongofsMongoClient")
    public MongoTemplate getMongoTemplate(MongoClient mongofsMongoClient,FileSystemProperties fileSystemProperties) {
        FileSystemProperties.Gridfs gridfs = fileSystemProperties.getGridfs();
        return new MongoTemplate(mongofsMongoClient, gridfs.getDatabase());
    }

    @Bean("mongofsSimpleMongoClientDbFactory")
    @ConditionalOnBean(name = "mongofsMongoClient")
    public SimpleMongoClientDbFactory getSimpleMongoClientDbFactory(MongoClient mongofsMongoClient,FileSystemProperties fileSystemProperties) {
        FileSystemProperties.Gridfs gridfs = fileSystemProperties.getGridfs();
        return new SimpleMongoClientDbFactory(mongofsMongoClient, gridfs.getDatabase());
    }

    @Bean(value = "mongofsMongoMappingContext")
    @ConditionalOnBean(name = "mongofsMongoClient")
    MongoMappingContext mongoMappingContext(ApplicationContext applicationContext,MongoProperties mongofsMongoProperties) throws ClassNotFoundException {
        MongoCustomConversions conversions = new MongoCustomConversions(Collections.emptyList());
        //PropertyMapper
        PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
        MongoMappingContext context = new MongoMappingContext();
        //mapper.from(properties.isAutoIndexCreation()).to(context::setAutoIndexCreation);
        mapper.from(mongofsMongoProperties.isAutoIndexCreation());
        context.setInitialEntitySet((new EntityScanner(applicationContext)).scan(new Class[]{Document.class, Persistent.class}));
        Class<?> strategyClass = mongofsMongoProperties.getFieldNamingStrategy();
        if (strategyClass != null) {
            context.setFieldNamingStrategy((FieldNamingStrategy) BeanUtils.instantiateClass(strategyClass));
        }
        context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
        return context;
    }

    @Bean("mongofsMappingMongoConverter")
    @ConditionalOnBean(name = "mongofsMongoClient")
    MappingMongoConverter mappingMongoConverter(SimpleMongoClientDbFactory mongofsSimpleMongoClientDbFactory, MongoMappingContext mongofsMongoMappingContext) {
        MongoCustomConversions conversions = new MongoCustomConversions(Collections.emptyList());
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongofsSimpleMongoClientDbFactory);
        MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, mongofsMongoMappingContext);
        mappingConverter.setCustomConversions(conversions);
        return mappingConverter;
    }



}
